package de.uka.ipd.sdq.reliability.solver.adaptivity;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import de.uka.ipd.sdq.markov.MarkovChain;
import de.uka.ipd.sdq.markov.State;
import de.uka.ipd.sdq.markov.Label;
import de.uka.ipd.sdq.markov.Transition;
import de.uka.ipd.sdq.reliability.solver.pcm2markov.MarkovBuilder;


public class MatlabConfiguration {

	private static final String AVAILABILITIES_t = "AVAILABILITIES_t";//ok
	private static final String WORKLOAD_FUNCTION_t = "WORKLOAD_FUNCTION_t";//ok
	private static final String LABELS = "LABELS";//ok
	private static final String N_NODES = "N_NODES";//ok
	private static final String AUTO_SCALING_GROUPS = "AUTO_SCALING_GROUPS";//ok
	private static final String SUCCESSFUL_NODES = "SUCCESSFUL_NODES";//ok
	private static final String FAILURE_NODES = "FAILURE_NODES";//ok
	private static final String CTRL_VARS = "CTRL_VARS";//ok
	private static final String CTRL_INIT_VALUES = "CTRL_INIT_VALUES";//ok
	private static final String AVAILABILITY_VARS = "AVAILABILITY_VARS";//ok
	private static final String COST_PER_MACHINE = "COST_PER_MACHINE";//ok
	private static final String MACHINE_STARTUP_TIME = "MACHINE_STARTUP_TIME";//ok
	private static final String REQS_COMPRESSION_RATE = "REQS_COMPRESSION_RATE";//ok
	private static final String MAX_WORDLOAD_CAPACITY_t = "MAX_WORDLOAD_CAPACITY_t";//ok
	
	private static final String SCALE_DOWN_BOUND_t = "SCALE_DOWN_BOUND_t";//ok
	private static final String SCALE_UP_BOUND_t = "SCALE_UP_BOUND_t";//ok	
	private static final String SYM_MATRIX = "SYM_MATRIX";//ok
	private static final String INIFINITE_CAPACITY_NODES = "INIFINITE_CAPACITY_NODES";//ok
	

	private Map<String,String> 	tokens;


	public MatlabConfiguration() {
		tokens = new HashMap<String, String>();		
	}

	public void init(MarkovChain chain,
			MarkovBuilder builder,
			Map<String, String> variableTypes,
			Map<String, String> variableNames,
			Map<String, String> variableBehaviour,
			Map<String, String> variableAttribute,
			Map<String, String> maxWorkload,
			Map<String, String> compressionRate,
			Map<String, Float> costPerVM, 
			Map<String, ArrayList<Float>> scaleUpBound, 
			Map<String, ArrayList<Float>> scaleDownBound,
			Map<String, Float> startupTime,
			Map<Transition, String> variables,
			int nonCtrlVars){

		ArrayList<Integer> tmp = new ArrayList<Integer>();				

		//load payload
		String payload="[";
		for(State s:chain.getStates())
			if(builder.getStartState(chain).equals(s))
				payload+=variableBehaviour.get("payload")+" ";
			else
				payload+="0 ";
		payload+="]";				
		tokens.put(WORKLOAD_FUNCTION_t, payload);

		//load dtmc size
		tokens.put(N_NODES,""+chain.getStates().size());		

		//load success state
		tmp.clear();
		tmp.add(chain.getStates().indexOf(builder.getSuccessState(chain)));
		tokens.put(SUCCESSFUL_NODES, toLogical(tmp,chain.getStates().size()));
		
		//load failure states
		tmp.clear();
		for(State s:builder.getFailureStates(chain))
			tmp.add(chain.getStates().indexOf(s));
		tokens.put(FAILURE_NODES, toLogical(tmp,chain.getStates().size()));

		//load labels, control variables, non control variables and initial values (default 1)
		String labels = "";
		
		String control = "[ ";
		String controlInitValues = "[ ";
		for(Transition t:variables.keySet()){
			String name = variables.get(t);
			if(name.startsWith("C")){
				control += name+" ";
				controlInitValues += "1 ";
				labels +=name+" ";
			}			
		}
		
		control += "]";
		controlInitValues += "]";
		tokens.put(CTRL_VARS,control);		
		tokens.put(CTRL_INIT_VALUES,controlInitValues);
		
		


		//load autoscaling groups and their properties

		tmp.clear();		
		String compression = "[ ";
		String cost = "[ ";
		String startUp = "[ ";
		String capacity = "[ ";
		ArrayList<Integer> tmpInfCapacity = new ArrayList<Integer>();
		for(State s:chain.getStates()){
			boolean assigned = false;
			for(Label l:s.getLabels())
				//if the state has an ID and it's associated to an autoscaling group
				if(!assigned && l.getKey().equals("ID") && variableAttribute.get(l.getValue()) != null){
					//add state index to tmp list
					if(variableAttribute.get(l.getValue()).equalsIgnoreCase("autoscaling")){
						tmp.add(chain.getStates().indexOf(s));
						cost += costPerVM.get(l.getValue())+" ";
						startUp += startupTime.get(l.getValue())+" ";
					}else{
						cost += "0 ";
						startUp += "0 ";
					}
					compression += compressionRate.get(l.getValue())+" ";
					if(maxWorkload.get(l.getValue()).equalsIgnoreCase("Inf"))
						tmpInfCapacity.add(chain.getStates().indexOf(s));
					capacity += maxWorkload.get(l.getValue())+" ";
					assigned = true;
				}
			if(!assigned){
				//write the default value in property arrays
				compression +="1 ";
				cost += "0 ";
				startUp += "0 ";
				capacity += "Inf ";	
				tmpInfCapacity.add(chain.getStates().indexOf(s));
			}
		}

		compression +="]";
		cost +="]";
		startUp +="]";
		capacity +="]";
		tokens.put(AUTO_SCALING_GROUPS, toLogical(tmp, chain.getStates().size()));
		tokens.put(REQS_COMPRESSION_RATE, compression);
		tokens.put(COST_PER_MACHINE, cost);
		tokens.put(MACHINE_STARTUP_TIME, startUp);
		tokens.put(MAX_WORDLOAD_CAPACITY_t, capacity);
		tokens.put(INIFINITE_CAPACITY_NODES, toLogical(tmpInfCapacity, chain.getStates().size()));
			
		//load availabilities 
		String availabilities = "[";
		for(State s:chain.getStates()){
			boolean def = true;
			for(Label l:s.getLabels()){
				if(l.getKey().equals("ID") && variableBehaviour.containsKey(l.getValue())){
					availabilities += variableBehaviour.get(l.getValue())+" ...\n";
					def = false;
				}
			}
			if(def)
				availabilities += "1 ...\n";
		}
		
		availabilities = availabilities.substring(0, availabilities.lastIndexOf("..."))+"]";
		tokens.put(AVAILABILITIES_t, availabilities);

		
		//load scale up
		//get number of rows
		int max_machine_number = 0;
		for(String s:scaleUpBound.keySet())
			if(scaleUpBound.get(s).size() > max_machine_number)
				max_machine_number = scaleUpBound.get(s).size();
		String scaleUp = "[";
		//build string
		for(int i=0;i<max_machine_number;i++){
			for(State s:chain.getStates()){
				boolean assigned = false;
				for(Label l:s.getLabels())
					if(!assigned && l.getKey().equals("ID") && scaleUpBound.containsKey(l.getValue()) && scaleUpBound.get(l.getValue()).size() > i){
						scaleUp += scaleUpBound.get(l.getValue()).get(i)+" ";
						assigned = true;
					}
				if(!assigned){
					scaleUp +="0 ";
					assigned = true;
				}
			}
			if(i<max_machine_number-1)
				scaleUp+=";\n";
			else
				scaleUp+=";";
		}
		scaleUp+="]";
		tokens.put(SCALE_UP_BOUND_t, scaleUp);
		
		
		
		//load scale down
		//get number of rows
		max_machine_number = 0;
		for(String s:scaleDownBound.keySet())
			if(scaleDownBound.get(s).size() > max_machine_number)
				max_machine_number = scaleDownBound.get(s).size();
		String scaleDown = "[";
		//build string
		for(int i=0;i<max_machine_number;i++){
			for(State s:chain.getStates()){
				boolean assigned = false;
				for(Label l:s.getLabels())
					if(!assigned && l.getKey().equals("ID") && scaleDownBound.containsKey(l.getValue()) && scaleDownBound.get(l.getValue()).size() > i){
						scaleDown += scaleDownBound.get(l.getValue()).get(i)+" ";
						assigned = true;
					}
				if(!assigned){
					scaleDown +="0 ";
					assigned = true;
				}
			}
			if(i<max_machine_number-1)
				scaleDown+=";\n";
			else
				scaleDown+=";";
		}
		scaleDown+="]";

		tokens.put(SCALE_DOWN_BOUND_t, scaleDown);

		//initialize the matrix
		String matrix[][] = new String[chain.getStates().size()][chain.getStates().size()];
		for(int i=0;i<chain.getStates().size();i++)
			for(int j=0;j<chain.getStates().size();j++)
				matrix[i][j]="0";
		//initialize non control vector
		String non_control = "[ ";
		//add variables and probabilities
		for(Transition t: chain.getTransitions())
			if(variables.containsKey(t) && t.getFromState() != t.getToState()){
				matrix[chain.getStates().indexOf(t.getFromState())][chain.getStates().indexOf(t.getToState())] = variables.get(t);
			}
			else
				matrix[chain.getStates().indexOf(t.getFromState())][chain.getStates().indexOf(t.getToState())] = ""+t.getProbability();

		int newVars = nonCtrlVars;
		for(int i=0;i<chain.getStates().size();i++){
			String var = null;
			for(int j=0;j<chain.getStates().size();j++)
				if(matrix[i][j].startsWith("a")||matrix[i][j].startsWith("C"))
					var = matrix[i][j]+" ";				
			if(var != null && var.startsWith("a")){
				non_control += var+" ";
				labels += var+" ";
			}
			else{
				non_control += "a"+newVars+" ";
				labels +="a"+newVars+" ";
				newVars++;
			}
		}

		
		non_control += "]";
		tokens.put(AVAILABILITY_VARS, non_control);
		tokens.put(LABELS,labels);

		//change non necessary probabilities with 1-sum of variables in each row
		for(int i=0;i<chain.getStates().size();i++){
			ArrayList<String> usedVariables = new ArrayList<String>();
			int index = -1;
			for(int j=0;j<chain.getStates().size();j++){
				if(matrix[i][j].startsWith("C") || matrix[i][j].startsWith("a"))
					usedVariables.add(matrix[i][j]);
				else if(!matrix[i][j].equals("0"))
					index = j;
			}
			if(index != -1 && usedVariables.size()>0){
				String newName = "1-(";
				for(String s:usedVariables)
					newName +=s+"+";
				newName = newName.substring(0,newName.lastIndexOf('+'))+")";
				matrix[i][index]=newName;
			}
		}
		
		//print the string
		String symMatrix="[";
		for(int i=0;i<chain.getStates().size();i++){
			for(int j=0;j<chain.getStates().size();j++){
				symMatrix += matrix[i][j]+" ";
				}							
			//symMatrix =symMatrix.substring(0,symMatrix.lastIndexOf(','));
			symMatrix +=";\n";
		}		
		symMatrix+= "]";
		tokens.put(SYM_MATRIX, symMatrix);

	}
	
	private String toLogical(ArrayList<Integer> numbers, int size){
		String logical = "[ ";
		for(int i=0;i<size;i++){
			if(numbers.contains(i))
				logical += "1 ";
			else 
				logical += "0 ";
		}
				
		logical +="]";
		return logical;
	}

	public  Boolean isValid(String Token){
		return tokens.containsKey(tokens);		
	}

	public String getValue(String token){
		return tokens.get(token);
	}


}

